package tinf.hamming;

import tinf.ispis.IspisUDatoteku;

/**
 * Razred implementira funkcionalnosti Hammingovog [7,4,3] kodera/dekodera.
 * 
 * @author Dado
 */
public class HammingCode743 {

	private static int[][] matricaH;
	private static int[][] transponiranaMatricaH;
	private static int n = 7;
	private static int k = 4;
	private static int brojRedakaMatriceH = n - k;

	/**
	 * Konstruktor. Kreira matricu H dimenzija (3x7) te matricu Ht.
	 */
	public HammingCode743() {
		matricaH = generirajMatricuH(n, k);
		transponiranaMatricaH = transponirajMatricu(matricaH,
				brojRedakaMatriceH, n);
	}

	/**
	 * Generira matricu provjere pariteta za Hammingov kod [n,k].
	 * 
	 * @param n
	 *            duljina kodne riječi
	 * @param k
	 *            broj podatkovnih bitova u kodnoj riječi
	 * @return matricu H
	 */
	private int[][] generirajMatricuH(int n, int k) {

		int[][] matrica = new int[n - k][n];

		for (int i = 1; i <= n; i++) {
			int q = i;
			for (int j = 0; j < n - k; j++) {
				matrica[j][i - 1] = q % 2;
				q /= 2;
			}
		}

		return matrica;
	}

	/**
	 * Transponira primljenu matricu danih dimenzija.
	 * 
	 * @param matrica
	 *            originalna matrica
	 * @param brojRedaka
	 *            broj redaka originalne matrice
	 * @param brojStupaca
	 *            broj stupaca originalne matrice
	 * @return transponiranu matricu
	 */
	private int[][] transponirajMatricu(int[][] matrica, int brojRedaka,
			int brojStupaca) {

		int[][] transponirana = new int[brojStupaca][brojRedaka];

		for (int i = 0; i < brojRedaka; i++) {
			for (int j = 0; j < brojStupaca; j++) {
				transponirana[j][i] = matrica[i][j];
			}
		}
		return transponirana;
	}

	/**
	 * Kodira ulazni niz bitova Hammingovim kodom [7,4,3].<br>
	 * Ukoliko duljina ulaznog niza nije djeljiva sa 4, niz se nadopunjuje
	 * potrebnim brojem nula.
	 * 
	 * @param ulaz
	 *            Ulazni niz bitova
	 * @return kodirani niz bitova (s dodanim zaštitnim bitovima)
	 */
	public String kodirajHammingom(String ulaz) {

		StringBuilder builder = new StringBuilder();
		String temp, blok, kodiraniBlok;
		temp = ulaz;

		if (temp.length() % 4 != 0) {
			temp = nadopuniNulama(temp);
		}

		IspisUDatoteku.upisiUDatoteku("Hamming[7,4,3] kodiranje");
		IspisUDatoteku.upisiUDatoteku("------------------------");

		while (temp.length() > 0) {
			blok = temp.substring(0, 4);
			kodiraniBlok = kodirajBlok(blok);
			builder.append(kodiraniBlok);
			temp = temp.substring(4);
		}

		IspisUDatoteku
				.upisiUDatoteku("========================================================================================================================\n");
		return builder.toString();
	}

	/**
	 * Nadopunjuje niz nulama dok mu duljina ne postane djeljiva sa 4.
	 * 
	 * @param temp
	 *            Ulazni niz
	 * @return nadopunjeni niz (djeljiv sa 4)
	 */
	private String nadopuniNulama(String temp) {
		while (temp.length() % 4 != 0) {
			temp += "0";
		}
		return temp;
	}

	/**
	 * Kodira blok od 4 podatkovna bita Hammingovim kodom [7,4,3].<br>
	 * Ulazni blok oblika je p0 p1 p2 p3.<br>
	 * Izlazni blok oblika je c0 c1 p0 c2 p1 p2 p3.<br>
	 * 
	 * @param blok
	 *            Ulazni blok duljine 4
	 * @return izlazni blok duljine 7
	 */
	private String kodirajBlok(String blok) {

		StringBuilder izlaz = new StringBuilder();

		int p0 = Integer.parseInt(blok.substring(0, 1));
		int p1 = Integer.parseInt(blok.substring(1, 2));
		int p2 = Integer.parseInt(blok.substring(2, 3));
		int p3 = Integer.parseInt(blok.substring(3));

		int c0 = (p0 + p1 + p3) % 2;
		int c1 = (p0 + p2 + p3) % 2;
		int c2 = (p1 + p2 + p3) % 2;

		izlaz.append(c0);
		izlaz.append(c1);
		izlaz.append(p0);
		izlaz.append(c2);
		izlaz.append(p1);
		izlaz.append(p2);
		izlaz.append(p3);

		IspisUDatoteku.upisiUDatoteku("poruka: " + blok + " --> kodna rijec: "
				+ izlaz.toString());
		// System.out.println("blok: " + blok + ", kodirani: " +
		// izlaz.toString());

		return izlaz.toString();
	}

	/**
	 * Prima čitavu kodiranu poruku, dekodira je te vraća poruku bez zaštitnih
	 * bitova.
	 * 
	 * @param kodiranaPoruka
	 *            Kodirana poruka
	 * @return dekodirana poruka (samo podatkovni bitovi)
	 */
	public String dekodirajHammingom(String kodiranaPoruka) {

		String sindrom, blok, ispravljenBlok, temp = kodiranaPoruka;
		StringBuilder bitoviPoruke = new StringBuilder();

		IspisUDatoteku.upisiUDatoteku("Hamming[7,4,3] dekodiranje");
		IspisUDatoteku.upisiUDatoteku("--------------------------");

		while (temp.length() > 0) {
			blok = temp.substring(0, 7);
			sindrom = dajSindrom(blok);
			if (!sindrom.equals("000")) {
				ispravljenBlok = ispraviPogresku(blok, sindrom);
			} else {
				ispravljenBlok = blok;
			}
			String bits = dohvatiPodatkovne(blok);
			if (ispravljenBlok != blok) {
				IspisUDatoteku.upisiUDatoteku("kodna rijec: " + blok
						+ ", ispravljena kodna rijec: " + ispravljenBlok
						+ " --> poruka: " + bits);
			} else {
				IspisUDatoteku.upisiUDatoteku("kodna rijec: " + blok
						+ " ------------------------------------> poruka: "
						+ bits);
			}

			bitoviPoruke.append(bits);
			temp = temp.substring(7);
		}

		String poruka = bitoviPoruke.toString();
		while (poruka.length() % 9 != 0) {
			poruka = poruka.substring(0, poruka.length() - 1);
		}
		IspisUDatoteku
				.upisiUDatoteku("========================================================================================================================\n");
		return poruka;
	}

	/**
	 * Nalazi sindrom kodiranog bloka duljine 7
	 * 
	 * @param kodiraniBlok
	 *            Kodirani blok
	 * @return sindrom bloka
	 */
	private String dajSindrom(String kodiraniBlok) {

		int[] bits = new int[7];
		int i = 0;
		for (char c : kodiraniBlok.toCharArray()) {
			bits[i++] = Integer.valueOf(String.valueOf(c));
		}

		return pomnoziSHT(bits);
	}

	/**
	 * Množi primljeni blok poruke s transponiranom matricom provjere pariteta
	 * (Ht).<br>
	 * 
	 * @param bits
	 *            Blok poruke (7 bitova)
	 * @return rezultat množenja (sindrom)
	 */
	private String pomnoziSHT(int[] bits) {

		int temp;
		StringBuilder sindrom = new StringBuilder();

		for (int j = 0; j < 3; j++) {
			temp = 0;
			for (int i = 0; i < 7; i++) {
				temp += bits[i] * transponiranaMatricaH[i][j];
			}
			sindrom.insert(0, temp % 2);
		}

		return sindrom.toString();
	}

	/**
	 * Ispravlja (jednostruku) pogrešku (invertira bit) bloka poruke na temelju
	 * zadanog sindroma.
	 * 
	 * @param blok
	 *            Blok poruke
	 * @param sindrom
	 *            Sindrom - pozicija pogreške
	 * @return ispravljen blok poruke
	 */
	private String ispraviPogresku(String blok, String sindrom) {

		StringBuilder temp = new StringBuilder(blok);
		int pozicija = Integer.parseInt(sindrom, 2);

		if (temp.charAt(pozicija - 1) == '1') {
			temp.setCharAt(pozicija - 1, '0');
		} else {
			temp.setCharAt(pozicija - 1, '1');
		}

		return temp.toString();
	}

	/**
	 * Dohvaća samo podatkovne bitove (njih 4) iz kodiranog bloka poruke.
	 * 
	 * @param blok
	 *            Kodirani blok
	 * @return podatkovni bitovi bloka
	 */
	private String dohvatiPodatkovne(String blok) {

		StringBuilder builder = new StringBuilder(blok);

		builder.deleteCharAt(3);
		builder.deleteCharAt(1);
		builder.deleteCharAt(0);

		return builder.toString();
	}

	/**
	 * test
	 */
	public static void main(String[] args) {

		HammingCode743 hc = new HammingCode743();

		matricaH = new int[brojRedakaMatriceH][n];
		matricaH = hc.generirajMatricuH(n, k);
		transponiranaMatricaH = new int[n][brojRedakaMatriceH];
		transponiranaMatricaH = hc.transponirajMatricu(matricaH,
				brojRedakaMatriceH, n);

		System.out.println("Matrica H:");
		for (int i = 0; i < brojRedakaMatriceH; i++) {
			for (int j = 0; j < n; j++) {
				System.out.print(matricaH[i][j] + " ");
			}
			System.out.println();
		}
		System.out.println();

		System.out.println("Matrica Ht:");
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < brojRedakaMatriceH; j++) {
				System.out.print(transponiranaMatricaH[i][j] + " ");
			}
			System.out.println();
		}

	}

}
